import json
import urllib2
import logging
import httplib
import urlparse
import oauth
import webbrowser
from urllib import urlencode
from binascii import b2a_base64
from data import DataManager
from base64 import b64encode,b64decode


OAUTH_GET_REQUEST_TOKEN_PATH = "/_ah/OAuthGetRequestToken"
OAUTH_AUTHORIZE_TOKEN_PATH = "/_ah/OAuthAuthorizeToken"
OAUTH_GET_ACCESS_TOKEN_PATH = "/_ah/OAuthGetAccessToken"
URLENCODED_TYPE = "application/x-www-form-urlencoded"


class InteractiveAuthRequired(Exception):
    pass

class APIProxy(object):
    def __init__(self, url, name=None):
        self.url = url
        self.name = name
        self.authenticated = False
        
    def __getattr__(self, name):
        return APIProxy(self.url, name)
    
    def __call__(self, *args):
        if not self.authenticated:
            self.authenticate()
        try:
            response = self.authorizer.post(urlparse.urljoin(self.url,self.name), json.dumps(args), {'Content-Type': 'application/json'})
        except IOError, e:
            response = '{"result":"error","error":"connection_refused","description":"%s"}' % e
        except Exception, e:
            response = '{"result":"error","error":"unknown"","description":"%s"}' % e
        logging.getLogger().debug(response)
        json_response = json.loads(response)
        if json_response["result"] == "error":
            if json_response["error"] == "invalid_credentials":
                raise InteractiveAuthRequired
 
        return json_response

    def _encode_access_token(self,access_token):
        return b64encode(access_token.key) + "|"+b64encode(access_token.secret)
    
    def _decode_access_token(self,string):
        try:
            enc_key,enc_secret = string.split("|")
            access_token = oauth.OAuthToken(b64decode(enc_key),b64decode(enc_secret))
        except Exception,e:
            logging.warning("Invalid access token found in config file. Run 'auth setup'")
            raise InteractiveAuthRequired
        return access_token

    def setup(self):
        host = urlparse.urlparse(self.url).netloc
        self.authorizer = Authorizer(self.url)
        self.authorizer.fetch_oauth_request_token()
        authorization_url = self.authorizer.get_oauth_authorize_url()
        print "Please, confirm authorization in the following URL:\n\n %s\n\nPress ENTER when done. " % authorization_url
        webbrowser.open_new_tab(authorization_url)
        raw_input()
        try:
            self.authorizer.fetch_oauth_access_token()
            access_token = self.authorizer.access_token
            DataManager.set("auth","access_token",self._encode_access_token(access_token))
            self.authenticated = True
            logging.info("Authentication setup successful.")
        except Exception,e:
            logging.error("Authentication failed: %s" % str(e))
            print "Authorization failed. Try again."

    def authenticate(self):
        access_token = self._decode_access_token(DataManager.get("auth","access_token"))
        if not access_token:
            logging.warning("No token found in local storage")
            raise InteractiveAuthRequired
        else:
            host = urlparse.urlparse(self.url).netloc
            self.authorizer = Authorizer(self.url,access_token)
            self.authenticated = True
            
class Authorizer(object):
    def __init__(self, url, access_token=None):
        self.consumer = oauth.OAuthConsumer("anonymous", "anonymous")
        self.request_token = None
        self.access_token = access_token
        self.url = url
        parts = urlparse.urlparse(url)
        self.oauth_url = "https://%s/" % (parts.netloc)

    def request(self, method, url, data=None, headers=None):
        body = None
        headers = headers.copy() if headers else {}
        if data:
            if method == "GET":
                url += "?" + urlencode(data, doseq=True)
            elif method == "POST":
                body = b2a_base64(data)
                content_headers = {'Content-Transfer-Encoding': 'base64'}
                headers.update(content_headers)
            else:
                raise ValueError("%s method is not supported" % method)
        self.sign_request(method, url, data, body, headers)
        request = urllib2.Request(url,data,headers)
        response = urllib2.urlopen(request)
        return response.read()        

    def get(self, url, data=None, headers=None):
        return self.request("GET", url, data, headers)

    def post(self, url, data=None, headers=None):
        return self.request("POST", url, data, headers)
    
    def fetch_oauth_request_token(self):
        response = self.get(urlparse.urljoin(self.oauth_url,OAUTH_GET_REQUEST_TOKEN_PATH))
        self.request_token = oauth.OAuthToken.from_string(response)
        return self.request_token

    def get_oauth_authorize_url(self):
        if self.request_token is None:
            raise RuntimeError("You should get request token first")

        url = urlparse.urljoin(self.oauth_url,OAUTH_AUTHORIZE_TOKEN_PATH)

        oauth_request = oauth.OAuthRequest.from_token_and_callback(
        token=self.request_token,
        http_url=url,
        )
        return oauth_request.to_url()

    def fetch_oauth_access_token(self):
        if self.request_token is None:
            raise RuntimeError("You should get request token first")
        response = self.get(urlparse.urljoin(self.oauth_url,OAUTH_GET_ACCESS_TOKEN_PATH))
        self.access_token = oauth.OAuthToken.from_string(response)
        return self.access_token

    def sign_request(self, method, url, data, body, headers):
        parameters = None
        if body is None or headers.get("Content-Type") == URLENCODED_TYPE:
            parameters = data

        signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()

        if self.access_token is not None:
            token = self.access_token
        elif self.request_token is not None:
            token = self.request_token
        else:
            token = None

        oauth_request = oauth.OAuthRequest.from_consumer_and_token(
        oauth_consumer=self.consumer,
        token=token,
        http_url=url,
        http_method=method,
        parameters=parameters,
        )
        oauth_request.sign_request(signature_method, self.consumer, token)
        headers.update(oauth_request.to_header())
